package com.github.difflib;

import static java.util.stream.Collectors.toList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.github.difflib.patch.AbstractDelta;
import com.github.difflib.patch.ChangeDelta;
import com.github.difflib.patch.Chunk;
import com.github.difflib.patch.DeleteDelta;
import com.github.difflib.patch.EqualDelta;
import com.github.difflib.patch.InsertDelta;
import com.github.difflib.patch.Patch;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipFile;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

public class DiffUtilsTest {

		@Test
		public void testDiff_Insert() {
				final Patch<String> patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays.asList("hhh", "jjj", "kkk"));
				assertNotNull(patch);
				assertEquals(1, patch.getDeltas().size());
				final AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof InsertDelta);
				assertEquals(new Chunk<>(1, Collections.<String>emptyList()), delta.getSource());
				assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget());
		}

		@Test
		public void testDiff_Delete() {
				final Patch<String> patch = DiffUtils.diff(Arrays.asList("ddd", "fff", "ggg"), Arrays.asList("ggg"));
				assertNotNull(patch);
				assertEquals(1, patch.getDeltas().size());
				final AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof DeleteDelta);
				assertEquals(new Chunk<>(0, Arrays.asList("ddd", "fff")), delta.getSource());
				assertEquals(new Chunk<>(0, Collections.<String>emptyList()), delta.getTarget());
		}

		@Test
		public void testDiff_Change() {
				final List<String> changeTest_from = Arrays.asList("aaa", "bbb", "ccc");
				final List<String> changeTest_to = Arrays.asList("aaa", "zzz", "ccc");

				final Patch<String> patch = DiffUtils.diff(changeTest_from, changeTest_to);
				assertNotNull(patch);
				assertEquals(1, patch.getDeltas().size());
				final AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof ChangeDelta);
				assertEquals(new Chunk<>(1, Arrays.asList("bbb")), delta.getSource());
				assertEquals(new Chunk<>(1, Arrays.asList("zzz")), delta.getTarget());
		}

		@Test
		public void testDiff_EmptyList() {
				final Patch<String> patch = DiffUtils.diff(new ArrayList<>(), new ArrayList<>());
				assertNotNull(patch);
				assertEquals(0, patch.getDeltas().size());
		}

		@Test
		public void testDiff_EmptyListWithNonEmpty() {
				final Patch<String> patch = DiffUtils.diff(new ArrayList<>(), Arrays.asList("aaa"));
				assertNotNull(patch);
				assertEquals(1, patch.getDeltas().size());
				final AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof InsertDelta);
		}

		@Test
		public void testDiffInline() {
				final Patch<String> patch = DiffUtils.diffInline("", "test");
				assertEquals(1, patch.getDeltas().size());
				assertTrue(patch.getDeltas().get(0) instanceof InsertDelta);
				assertEquals(0, patch.getDeltas().get(0).getSource().getPosition());
				assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size());
				assertEquals("test", patch.getDeltas().get(0).getTarget().getLines().get(0));
		}

		@Test
		public void testDiffInline2() {
				final Patch<String> patch = DiffUtils.diffInline("es", "fest");
				assertEquals(2, patch.getDeltas().size());
				assertTrue(patch.getDeltas().get(0) instanceof InsertDelta);
				assertEquals(0, patch.getDeltas().get(0).getSource().getPosition());
				assertEquals(2, patch.getDeltas().get(1).getSource().getPosition());
				assertEquals(0, patch.getDeltas().get(0).getSource().getLines().size());
				assertEquals(0, patch.getDeltas().get(1).getSource().getLines().size());
				assertEquals("f", patch.getDeltas().get(0).getTarget().getLines().get(0));
				assertEquals("t", patch.getDeltas().get(1).getTarget().getLines().get(0));
		}

		@Test
		public void testDiffIntegerList() {
				List<Integer> original = Arrays.asList(1, 2, 3, 4, 5);
				List<Integer> revised = Arrays.asList(2, 3, 4, 6);

				final Patch<Integer> patch = DiffUtils.diff(original, revised);

				for (AbstractDelta<Integer> delta : patch.getDeltas()) {
						System.out.println(delta);
				}

				assertEquals(2, patch.getDeltas().size());
				assertEquals(
								"[DeleteDelta, position: 0, lines: [1]]",
								patch.getDeltas().get(0).toString());
				assertEquals(
								"[ChangeDelta, position: 4, lines: [5] to [6]]",
								patch.getDeltas().get(1).toString());
		}

		@Test
		public void testDiffMissesChangeForkDnaumenkoIssue31() {
				List<String> original = Arrays.asList("line1", "line2", "line3");
				List<String> revised = Arrays.asList("line1", "line2-2", "line4");

				Patch<String> patch = DiffUtils.diff(original, revised);
				assertEquals(1, patch.getDeltas().size());
				assertEquals(
								"[ChangeDelta, position: 1, lines: [line2, line3] to [line2-2, line4]]",
								patch.getDeltas().get(0).toString());
		}

		/**
		 * To test this, the greedy Myer algorithm is not suitable.
		 */
		@Test
		@Disabled
		public void testPossibleDiffHangOnLargeDatasetDnaumenkoIssue26() throws IOException {
				ZipFile zip = new ZipFile(TestConstants.MOCK_FOLDER + "/large_dataset1.zip");

				Patch<String> patch = DiffUtils.diff(
								readStringListFromInputStream(zip.getInputStream(zip.getEntry("ta"))),
								readStringListFromInputStream(zip.getInputStream(zip.getEntry("tb"))));

				assertEquals(1, patch.getDeltas().size());
		}

		public static List<String> readStringListFromInputStream(InputStream is) throws IOException {
				try (BufferedReader reader =
								new BufferedReader(new InputStreamReader(is, Charset.forName(StandardCharsets.UTF_8.name())))) {

						return reader.lines().collect(toList());
				}
		}

		@Test
		public void testDiffMyersExample1() {
				final Patch<String> patch = DiffUtils.diff(
								Arrays.asList("A", "B", "C", "A", "B", "B", "A"), Arrays.asList("C", "B", "A", "B", "A", "C"));
				assertNotNull(patch);
				assertEquals(4, patch.getDeltas().size());
				assertEquals(
								"Patch{deltas=[[DeleteDelta, position: 0, lines: [A, B]], [InsertDelta, position: 3, lines: [B]], [DeleteDelta, position: 5, lines: [B]], [InsertDelta, position: 7, lines: [C]]]}",
								patch.toString());
		}

		@Test
		public void testDiff_Equal() {
				final Patch<String> patch =
								DiffUtils.diff(Arrays.asList("hhh", "jjj", "kkk"), Arrays.asList("hhh", "jjj", "kkk"), true);
				assertNotNull(patch);
				assertEquals(1, patch.getDeltas().size());
				final AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof EqualDelta);
				assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getSource());
				assertEquals(new Chunk<>(0, Arrays.asList("hhh", "jjj", "kkk")), delta.getTarget());
		}

		@Test
		public void testDiff_InsertWithEqual() {
				final Patch<String> patch = DiffUtils.diff(Arrays.asList("hhh"), Arrays.asList("hhh", "jjj", "kkk"), true);
				assertNotNull(patch);
				assertEquals(2, patch.getDeltas().size());

				AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof EqualDelta);
				assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getSource());
				assertEquals(new Chunk<>(0, Arrays.asList("hhh")), delta.getTarget());

				delta = patch.getDeltas().get(1);
				assertTrue(delta instanceof InsertDelta);
				assertEquals(new Chunk<>(1, Collections.<String>emptyList()), delta.getSource());
				assertEquals(new Chunk<>(1, Arrays.asList("jjj", "kkk")), delta.getTarget());
		}

		@Test
		public void testDiff_ProblemIssue42() {
				final Patch<String> patch = DiffUtils.diff(
								Arrays.asList("The", "dog", "is", "brown"), Arrays.asList("The", "fox", "is", "down"), true);

				System.out.println(patch);
				assertNotNull(patch);
				assertEquals(4, patch.getDeltas().size());

				assertThat(patch.getDeltas())
								.extracting(d -> d.getType().name())
								.containsExactly("EQUAL", "CHANGE", "EQUAL", "CHANGE");

				AbstractDelta<String> delta = patch.getDeltas().get(0);
				assertTrue(delta instanceof EqualDelta);
				assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getSource());
				assertEquals(new Chunk<>(0, Arrays.asList("The")), delta.getTarget());

				delta = patch.getDeltas().get(1);
				assertTrue(delta instanceof ChangeDelta);
				assertEquals(new Chunk<>(1, Arrays.asList("dog")), delta.getSource());
				assertEquals(new Chunk<>(1, Arrays.asList("fox")), delta.getTarget());

				delta = patch.getDeltas().get(2);
				assertTrue(delta instanceof EqualDelta);
				assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getSource());
				assertEquals(new Chunk<>(2, Arrays.asList("is")), delta.getTarget());

				delta = patch.getDeltas().get(3);
				assertTrue(delta instanceof ChangeDelta);
				assertEquals(new Chunk<>(3, Arrays.asList("brown")), delta.getSource());
				assertEquals(new Chunk<>(3, Arrays.asList("down")), delta.getTarget());
		}

		@Test
		public void testDiffPatchIssue189Problem() throws IOException {
				String original = new String(Files.readAllBytes(
								Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_original.txt")));
				String revised = new String(Files.readAllBytes(
								Paths.get("target/test-classes/com/github/difflib/text/issue_189_insert_revised.txt")));

				Patch patch = DiffUtils.diff(Arrays.asList(original.split("\n")), Arrays.asList(revised.split("\n")));

				assertEquals(1, patch.getDeltas().size());
		}
}
